﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Web.Script.Serialization;
using System.Windows.Forms;

namespace ScanStock
{
    public partial class FormRadar : Form
    {
        BackgroundWorker worker = null;
        Scanner scanner = null;
        string strJuheUrlFormat = string.Empty;
        delegate void stop_callback();
        public static string strRootDir;
        public static string strWorkDir;

        public FormRadar()
        {
            StreamReader sr;
            InitializeComponent();
            try
            {
                sr = new StreamReader(@"c:\juhe_url.txt");
                strJuheUrlFormat = sr.ReadToEnd();
                sr.Close();
            }
            catch (Exception)
            {
                MessageBox.Show(@"无法读取 c:\juhe_url.txt 文件!!");
            }
            if (strJuheUrlFormat.Length == 0)
            {
                button1.Enabled = false;
                textBox1.Text = "缺乏配置文件，无法启动";
            }
            string path = Directory.GetCurrentDirectory();
            int j = path.IndexOf('\\');
            strWorkDir = path.Substring(0, j);
            strWorkDir += @"\Clockwise";
            if (!Directory.Exists(strWorkDir))
            {
                Directory.CreateDirectory(strWorkDir);
            }
            strRootDir = strWorkDir;
            strWorkDir += @"\";
            strWorkDir += DateTime.Now.ToString(@"yyyyMMdd");
            if (!Directory.Exists(strWorkDir))
            {
                Directory.CreateDirectory(strWorkDir);
            }
            CenterToScreen();
        }

        public void SetText(string str)
        {
            textBox1.Text = str;
        }

        public void SetValue(int percent)
        {
            progressBar1.Value = percent;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (worker == null)
            {
                worker = new BackgroundWorker(); 
                scanner = new Scanner(checkBox1.Checked, checkBox2.Checked, checkBox3.Checked,strJuheUrlFormat, this);
                worker.DoWork += scanner.DoWork;
                worker.WorkerReportsProgress = true;
                worker.WorkerSupportsCancellation = true; 
                worker.ProgressChanged += scanner.ProgressChanged;
                worker.RunWorkerAsync();
                progressBar1.Maximum = 100;
                progressBar1.Visible = true;
                textBox1.Text = "开始准备扫描";
                button1.Text = "停止";
                if (checkBox1.Checked && !Directory.Exists(strWorkDir + @"\wShTick"))
                {
                    Directory.CreateDirectory(strWorkDir + @"\wShTick");
                }
                if (checkBox1.Checked && !Directory.Exists(strWorkDir + @"\wSzTick"))
                {
                    Directory.CreateDirectory(strWorkDir + @"\wSzTick");
                }
                if (checkBox2.Checked && !Directory.Exists(strWorkDir + @"\wHkTick"))
                {
                    Directory.CreateDirectory(strWorkDir + @"\wHkTick");
                }
                if (checkBox3.Checked && !Directory.Exists(strWorkDir + @"\wUsTick"))
                {
                    Directory.CreateDirectory(strWorkDir + @"\wUsTick");
                }
                if (checkBox1.Checked && !Directory.Exists(strRootDir + @"\wShDays"))
                {
                    Directory.CreateDirectory(strRootDir + @"\wShDays");
                }
                if (checkBox1.Checked && !Directory.Exists(strRootDir + @"\wSzDays"))
                {
                    Directory.CreateDirectory(strRootDir + @"\wSzDays");
                }
                if (checkBox2.Checked && !Directory.Exists(strRootDir + @"\wHkDays"))
                {
                    Directory.CreateDirectory(strRootDir + @"\wHkDays");
                }
                if (checkBox3.Checked && !Directory.Exists(strRootDir + @"\wUsDays"))
                {
                    Directory.CreateDirectory(strRootDir + @"\wUsDays");
                }
            }
            else
            {
                worker.CancelAsync();
                progressBar1.Visible = false;
                textBox1.Text = "停止扫描";
                button1.Text = "启动";
                scanner = null;
                worker = null;
            }
            checkBox1.Enabled = worker == null;
            checkBox2.Enabled = worker == null;
            checkBox3.Enabled = worker == null;
        }

        public bool GetRoundEnd()
        {
            return checkBox4.Checked;
        }

        public void stop()
        {
            if (progressBar1.InvokeRequired)
            {
                stop_callback cb = new stop_callback(stop);
                Invoke(cb, new object[] { });
                return;
            }
            worker.CancelAsync();
            progressBar1.Visible = false;
            textBox1.Text = "停止扫描";
            button1.Text = "启动";
            scanner = null;
            worker = null;
            checkBox1.Enabled = true;
            checkBox2.Enabled = true;
            checkBox3.Enabled = true;
            checkBox4.Checked = false;
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (worker != null)
            {
                e.Cancel = true;
                MessageBox.Show("停止后才能退出");
            }
        }
    }

    class Scanner
    {
        string strUrlFormat = string.Empty;
        volatile bool bCnOpen, bHkOpen, bUsOpen;
        int mMarket=0;
        FormRadar form1;
        SaveStockInfoCn shInfo = null;
        SaveStockInfoCn szInfo = null;
        SaveStockInfoHk hkInfo = null;
        SaveStockInfoUs usInfo = null;
        int[] mStockCount = new int[4];
        int[] mStockPage = new int[4];

        public Scanner(bool cn, bool hk, bool us, string url, FormRadar f1)
        {
            bCnOpen = cn;
            bHkOpen = hk;
            bUsOpen = us;
            strUrlFormat = url;
            form1 = f1;
        }

        public void DoWork(object sender, DoWorkEventArgs e)
        {
            int page = 0;
            int type;
            int wait;
            int i;
            const int waitMax = 100;
            string strMsg;
            string strUrl;
            BackgroundWorker worker = sender as BackgroundWorker;
            if (bCnOpen)
            {
                shInfo = new SaveStockInfoCn(0);
                szInfo = new SaveStockInfoCn(1);
            }
            if (bHkOpen)
            {
                hkInfo = new SaveStockInfoHk();
            }
            if (bUsOpen)
            {
                usInfo = new SaveStockInfoUs();
            }
            while (true)
            {
                wait = 1;
                strMsg = string.Empty;
                strUrl = string.Empty;
                switch(mMarket)
                {
                    case 0:
                        if (bCnOpen)
                        {
                            type = 4;
                            strUrl = string.Format(strUrlFormat, "sh", page+1, type);
                        }
                        else mMarket++;
                        break;
                    case 1:
                        if (bCnOpen)
                        {
                            type = 4;
                            strUrl = string.Format(strUrlFormat, "sz", page + 1, type);
                        }
                        else mMarket++;
                        break;
                    case 2:
                        if (bHkOpen)
                        {
                            type = 4;
                            strUrl = string.Format(strUrlFormat, "hk", page + 1, type);
                        }
                        else mMarket++;
                        break;
                    case 3:
                        if (bUsOpen)
                        {
                            type = 3;
                            strUrl = string.Format(strUrlFormat, "usa", page + 1, type);
                        }
                        else mMarket++;
                        break;
                    default:
                        mMarket = 0;
                        wait = waitMax;
                        break;
                }
                if (strUrl.Length > 0)
                {
                    string[] strMarket = { "沪股", "深股", "港股", "美股" };
                    string strResult = DoGetRequestSendData(strUrl, mMarket);
                    int max = ParseResult(strResult, mMarket);
                    if (mStockPage[mMarket] <= 0) mStockPage[mMarket] = max;
                    if (worker.CancellationPending == true)
                    {
                        e.Cancel = true;
                        break;
                    }
                    strMsg = string.Format("扫描 {0} 第 {1} 页/共 {2} 页", strMarket[mMarket], page + 1, mStockPage[mMarket]);
                    worker.ReportProgress(mStockPage[mMarket] > 0?(page+1)*100/ mStockPage[mMarket] : 0, strMsg);
                    if (++page >= mStockPage[mMarket])
                    {
                        page = 0;
                        mMarket++;
                        wait = waitMax;
                    }
                }
                for(i=0; i<wait; i++)
                {
                    if (worker.CancellationPending == true)
                    {
                        e.Cancel = true;
                        i = -1;
                        break;
                    }
                    Thread.Sleep(10);
                    if (i == waitMax - 1)
                    {
                        if(form1.GetRoundEnd())
                        {
                            // 跑完一趟即停
                            e.Cancel = true;
                            i = -1;
                            form1.stop();
                            break;
                        }
                        worker.ReportProgress(0, "新一轮巡航即将开始啦 ...");
                    }
                }
                if (i < 0) break;
            }
            if (bCnOpen)
            {
                shInfo.SaveDays();
                szInfo.SaveDays();
            }
            if (bHkOpen)
            {
                hkInfo.SaveDays();
            }
            if (bUsOpen)
            {
                usInfo.SaveDays();
            }
        }

        public void ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            form1.SetValue(e.ProgressPercentage);
            string message = e.UserState.ToString();
            form1.SetText(message);
        }
        
        public string DoGetRequestSendData(string url, int market)
        {
            HttpWebRequest hwRequest = null;
            HttpWebResponse hwResponse = null;
            string strResult = string.Empty;
            try
            {
                hwRequest = (System.Net.HttpWebRequest)WebRequest.Create(url);
                hwRequest.Method = "GET";
                hwResponse = (HttpWebResponse)hwRequest.GetResponse();
                StreamReader srReader = new StreamReader(hwResponse.GetResponseStream(), Encoding.UTF8);
                strResult = srReader.ReadToEnd();
                srReader.Close();
                hwResponse.Close();
                return strResult;
            }
            catch (System.Exception err)
            {
                err.ToString();
            }
            return strResult;
        }

        public void ParseResult()
        {
            string strTest = "{\"resultcode\":\"404\",\"reason\":\"请求地址错误\",\"result\":[],\"error_code\":404}";
            ParseResult(strTest, 0);
        }

        int ParseResult(string str, int market)
        {
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            StockResult sr = serializer.Deserialize<StockResult>(str);
            if (sr != null && sr.error_code == 0)
            {
                int nLinePerPage = 0;
                int nStockCount = 0;
                switch (market)
                {
                    case 0:
                    case 1:
                        StockCheckCn sccn = serializer.Deserialize<StockCheckCn>(str);
                        nLinePerPage = Convert.ToInt32(sccn.result.num);
                        nStockCount = Convert.ToInt32(sccn.result.totalCount);
                        if (market == 0)
                        {
                            shInfo.Accept(sccn);
                        }
                        if (market == 1)
                        {
                            szInfo.Accept(sccn);
                        }
                        break;
                    case 2:
                        StockCheckHk schk = serializer.Deserialize<StockCheckHk>(str);
                        nLinePerPage = Convert.ToInt32(schk.result.num);
                        nStockCount = Convert.ToInt32(schk.result.totalCount);
                        hkInfo.Accept(schk);
                        break;
                    case 3:
                        StockCheckUs scus = serializer.Deserialize<StockCheckUs>(str);
                        nLinePerPage = Convert.ToInt32(scus.result.num);
                        nStockCount = Convert.ToInt32(scus.result.totalCount);
                        usInfo.Accept(scus);
                        break;
                }
                mStockCount[market] = nStockCount;
                return (nStockCount + nLinePerPage - 1) / nLinePerPage;
            }
            return -1;
        }
    }
    
    sealed class StockResult
    {
        public int error_code = 0;
    }

    sealed class StockCheckCn
    {
        public StockInfoCn result = null;
    }

    sealed class StockInfoCn
    {
        public string totalCount = null;
        public string page = null;
        public string num = null;
        public List<StockDataCn> data = null;
    }

    sealed class StockDataCn
    {
        public string symbol = null;
        public string name = null;
        public string trade = null;
        public string pricechange = null;
        public string changepercent = null;
        public string buy = null;
        public string sell = null;
        public string settlement = null;
        public string open = null;
        public string high = null;
        public string low = null;
        public string volume = null;
        public string amount = null;
        public string code = null;
        public string ticktime = null;

        public string GetString()
        {
            return symbol + "," + name + "," + trade + "," + pricechange + "," + changepercent + "," + buy + "," + sell + "," + settlement + "," + open + "," + high + "," + low + "," + volume + "," + amount + "," + code + "," + ticktime;
        }
    }

    sealed class StockCheckHk
    {
        public StockInfoHk result = null;
    }

    class StockInfoHk
    {
        public string totalCount = null;
        public string page = null;
        public string num = null;
        public List<StockDataHk> data = null;
    }

    sealed class StockDataHk
    {
        public string symbol = null;
        public string name = null;
        public string engname = null;
        public string tradetype = null;
        public string lasttrade = null;
        public string prevclose = null;
        public string open = null;
        public string high = null;
        public string low = null;
        public string volume = null;
        public string currentvolume = null;
        public string amount = null;
        public string ticktime = null;
        public string buy = null;
        public string sell = null;
        public string high_52week = null;
        public string low_52week = null;
        public string eps = null;
        public string dividend = null;
        public string stocks_sum = null;
        public string pricechange = null;
        public string changepercent = null;

        public string GetString()
        {
            return symbol + "," + name + "," + engname + "," + tradetype + "," + lasttrade + "," + prevclose + "," + open + "," + high + "," + low + "," + volume + "," + currentvolume + "," + amount + "," + ticktime + "," + buy + "," + sell + "," + high_52week + "," + low_52week + "," + eps + "," + dividend + "," + stocks_sum + "," + pricechange + "," + changepercent;
        }
    }

    sealed class StockCheckUs
    {
        public StockInfoUs result = null;
    }

    sealed class StockInfoUs
    {
        public string totalCount = null;
        public string page = null;
        public string num = null;
        public List<StockDataUs> data = null;
    }

    sealed class StockDataUs
    {
        public string cname = null;
        public string category = null;
        public string symbol = null;
        public string price = null;
        public string diff = null;
        public string chg = null;
        public string preclose = null;
        public string open = null;
        public string high = null;
        public string low = null;
        public string amplitude = null;
        public string volume = null;
        public string mktcap = null;  // 市值
        public string market = null;

        public string GetString()
        {
            return symbol + "," + cname + "," + category + "," + price + "," + diff + "," + chg + "," + preclose + "," + open + "," + high + "," + low + "," + amplitude + "," + volume + "," + mktcap + "," + market;
        }
    }

    class StockDataMd5
    {
        public string md5 = null;
        public string code = null;
        public string name = null;
        public string category = null;
        public float price = 0;
        public float diff = 0;
        public float chg = 0;
        public float preclose = 0;
        public float open = 0;
        public float high = 0;
        public float low = 0;
        public float amplitude = 0;
        public float volume = 0;
        public float amount = 0;
        public float buy = 0;
        public float sell = 0;
        public float delta = 0;
        public float buy2 = 0;
        public float buy3 = 0;
        public float buy4 = 0;
        public float buy5 = 0;
        public float sell2 = 0;
        public float sell3 = 0;
        public float sell4 = 0;
        public float sell5 = 0;
        public float buyV = 0;
        public float buyV2 = 0;
        public float buyV3 = 0;
        public float buyV4 = 0;
        public float buyV5 = 0;
        public float sellV = 0;
        public float sellV2 = 0;
        public float sellV3 = 0;
        public float sellV4 = 0;
        public float sellV5 = 0;
        public float time = 0;

        public static byte[] float2bytes(float f)
        {
            return BitConverter.GetBytes(f);
        }

        public const int mStockDataSize = 256;
        public const int mTickDataSize = 128;

        public byte[] GetStockData()
        {
            byte[] data = new byte[mStockDataSize];
            System.Array.Copy(GetTickData(), 0, data, 0, mTickDataSize);
            byte[] buff = System.Text.Encoding.UTF8.GetBytes(name);
            int len = buff.Length;
            if (len > 30) len = 30;
            System.Array.Copy(buff, 0, data, 218, len);
            buff = System.Text.Encoding.ASCII.GetBytes(code);
            len = buff.Length;
            if (len > 8) len = 8;
            System.Array.Copy(buff, 0, data, 248, len);
            return data;
        }

        public byte[] GetTickData()
        {
            byte[] data = new byte[mTickDataSize];
            int pos = 0;
            System.Array.Copy(float2bytes(time), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(price), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(diff), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(chg), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(preclose), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(open), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(high), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(low), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(amplitude), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(volume), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(amount), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(buy), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(sell), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(delta), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(buy2), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(buy3), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(buy4), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(buy5), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(sell2), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(sell3), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(sell4), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(sell5), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(buyV), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(buyV2), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(buyV3), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(buyV4), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(buyV5), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(sellV), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(sellV2), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(sellV3), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(sellV4), 0, data, pos, 4); pos += 4;
            System.Array.Copy(float2bytes(sellV5), 0, data, pos, 4);
            return data;
        }
    }

    sealed class StockDataCnMd5 : StockDataMd5
    {
        public StockDataCn data;

        public void ConvertData()
        {
            price = Convert.ToSingle(data.trade);
            diff = Convert.ToSingle(data.pricechange);
            chg = Convert.ToSingle(data.changepercent);
            preclose = Convert.ToSingle(data.settlement);
            open = Convert.ToSingle(data.open);
            high = Convert.ToSingle(data.high);
            low = Convert.ToSingle(data.low);
            buy = Convert.ToSingle(data.buy);
            sell = Convert.ToSingle(data.sell);
            volume = delta = Convert.ToSingle(data.volume);
            amount = Convert.ToSingle(data.amount);
            amplitude = low > 0 ? (high - low) * 100 / low : 0;
            time = Convert.ToSingle(data.ticktime.Replace(":", ""));
            if (code == null) code = data.code;
            if (name == null) name = data.name;
        }
    }

    // 在内存和文件保存数据

    class SaveStockInfo
    {
        protected int mStockMarket = 0;
        protected int mStockCount = 0;
        protected const int mStockDataSize = StockDataMd5.mStockDataSize;  // 全部数据
        protected const int mTickDataSize = StockDataMd5.mTickDataSize;  // 动态数据
        protected int mDataDate;

        public SaveStockInfo(int nMarket)
        {
            this.mStockMarket = nMarket;
            mDataDate = DateTime.Now.Year * 10000 + DateTime.Now.Month * 100 + DateTime.Now.Day;
        }

        public void SetStockCount(int count)
        {
            mStockCount = count;
        }

        public string GetMarketDataFileName()
        {
            string path = FormRadar.strWorkDir;
            switch (mStockMarket)
            {
                case 0:
                    path += @"\wShInfo.dat";
                    break;
                case 1:
                    path += @"\wSzInfo.dat";
                    break;
                case 2:
                    path += @"\wHkInfo.dat";
                    break;
                case 3:
                    path += @"\wUsInfo.dat";
                    break;
            }
            return path;
        }

        public string GetMarketInfoFileName()
        {
            string path = FormRadar.strWorkDir;
            switch (mStockMarket)
            {
                case 0:
                    path += @"\wShInfo.txt";
                    break;
                case 1:
                    path += @"\wSzInfo.txt";
                    break;
                case 2:
                    path += @"\wHkInfo.txt";
                    break;
                case 3:
                    path += @"\wUsInfo.txt";
                    break;
            }
            return path;
        }

        public void OutputData(FileStream fs, int idx, StockDataMd5 data, bool full)
        {
            byte[] by = full ? data.GetStockData() : data.GetTickData();
            int seek = idx * mStockDataSize;
            fs.Seek(seek, SeekOrigin.Begin);
            fs.Write(by, 0, by.Length);
            if(!full) fs.Write(System.Text.Encoding.ASCII.GetBytes(data.code), 0, 6);
        }

        public byte[] Load()
        {
            string path = GetMarketDataFileName();
            FileStream fs = new FileStream(
                   path,
                   FileMode.OpenOrCreate,
                   FileAccess.Read,
                   FileShare.Read);
            mStockCount = (int)fs.Length / mStockDataSize;
            byte[] buff = new byte[fs.Length];
            fs.Read(buff, 0, (int)fs.Length);
            fs.Close();
            return buff;
        }

        protected void SaveDaysItem(string code, float date, float price, float preclose, float open, float high, float low, float volume, float amount)
        {
            string path = FormRadar.strRootDir;
            switch (mStockMarket)
            {
                case 0:
                    path += @"\wShDays";
                    break;
                case 1:
                    path += @"\wSzDays";
                    break;
                case 2:
                    path += @"\wHkDays";
                    break;
                case 3:
                    path += @"\wUsDays";
                    break;
            }
            FileStream fsDays;
            byte[] days = new byte[32];
            int pos = 0;
            System.Array.Copy(StockDataMd5.float2bytes(date), 0, days, pos, 4); pos += 4;
            System.Array.Copy(StockDataMd5.float2bytes(preclose), 0, days, pos, 4); pos += 4;
            System.Array.Copy(StockDataMd5.float2bytes(open), 0, days, pos, 4); pos += 4;
            System.Array.Copy(StockDataMd5.float2bytes(high), 0, days, pos, 4); pos += 4;
            System.Array.Copy(StockDataMd5.float2bytes(low), 0, days, pos, 4); pos += 4;
            System.Array.Copy(StockDataMd5.float2bytes(price), 0, days, pos, 4); pos += 4;
            System.Array.Copy(StockDataMd5.float2bytes(volume), 0, days, pos, 4); pos += 4;
            System.Array.Copy(StockDataMd5.float2bytes(amount), 0, days, pos, 4);
            string name = path + @"\" + code + (mStockMarket == 3 ? "~.dat" : ".dat");
            if (File.Exists(name))
            {
                fsDays = new FileStream(name, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                byte[] temp = new byte[32];
                if (fsDays.Length > days.Length) fsDays.Seek(fsDays.Length - days.Length, SeekOrigin.Begin);
                fsDays.Read(temp, 0, temp.Length);
                float fdate = BitConverter.ToSingle(temp, 0);
                if ((int)fdate == (int)date)
                {
                    fsDays.Seek(fsDays.Length - days.Length, SeekOrigin.Begin);
                }
                else
                {
                    fsDays.Seek(0, SeekOrigin.Begin);
                }
                fsDays.Write(days, 0, days.Length);
                fsDays.Close();
            } 
            else
            {
                fsDays = new FileStream(name, FileMode.Append);
                fsDays.Write(days, 0, days.Length);
                fsDays.Close();
            }
        }
    }

    sealed class SaveStockInfoCn : SaveStockInfo
    {
        public List<StockDataMd5> data = null;

        public SaveStockInfoCn(int nMarket) : base(nMarket)
        {
            int pos, seek;
            StockDataCnMd5 sdcnmd5;
            data = new List<StockDataMd5>();
            byte[] buff = Load();
            for (int i = 0; i < mStockCount; i++)
            {
                sdcnmd5 = new StockDataCnMd5();
                seek = StockDataMd5.mStockDataSize * i;
                pos = seek + 248; while (pos < buff.Length) { if (buff[pos] == 0) break; pos++; }
                sdcnmd5.code = System.Text.Encoding.ASCII.GetString(buff, seek + 248, pos - seek - 248);
                pos = seek + 218; while (pos < buff.Length) { if (buff[pos] == 0) break; pos++; }
                sdcnmd5.name = System.Text.Encoding.UTF8.GetString(buff, seek + 218, pos - seek - 218);
                pos = seek;
                sdcnmd5.time = BitConverter.ToSingle(buff, pos);
                sdcnmd5.price = BitConverter.ToSingle(buff, pos + 4);
                sdcnmd5.diff = BitConverter.ToSingle(buff, pos + 8);
                sdcnmd5.chg = BitConverter.ToSingle(buff, pos + 12);
                sdcnmd5.preclose = BitConverter.ToSingle(buff, pos + 16);
                sdcnmd5.open = BitConverter.ToSingle(buff, pos + 20);
                sdcnmd5.high = BitConverter.ToSingle(buff, pos + 24);
                sdcnmd5.low = BitConverter.ToSingle(buff, pos + 28);
                sdcnmd5.amplitude = BitConverter.ToSingle(buff, pos + 32);
                sdcnmd5.volume = BitConverter.ToSingle(buff, pos + 36);
                sdcnmd5.amount = BitConverter.ToSingle(buff, pos + 40);
                sdcnmd5.buy = BitConverter.ToSingle(buff, pos + 44);
                sdcnmd5.sell = BitConverter.ToSingle(buff, pos + 48);
                sdcnmd5.delta = BitConverter.ToSingle(buff, pos + 52);
                data.Add(sdcnmd5);
            }
        }

        public void Accept(StockCheckCn sc)
        {
            int i, j, k = 0;
            bool bNeedOutput = false;
            string path = GetMarketDataFileName();
            FileStream fs = new FileStream(
                   path,
                   FileMode.OpenOrCreate,
                   FileAccess.Write,
                   FileShare.Read);
            foreach (StockDataCn sdcn in sc.result.data)
            {
                StockDataCnMd5 sdcnmd5Temp = new StockDataCnMd5();
                sdcnmd5Temp.data = sdcn;
                sdcnmd5Temp.md5 = StockTools.CreateMD5Hash(sdcn.GetString());
                sdcnmd5Temp.ConvertData();
                for (i = 0; i < data.Count; i++)
                {
                    j = k;
                    StockDataCnMd5 sdcnmd5Item = (StockDataCnMd5) data[j];
                    if (++k >= data.Count) k = 0;
                    if (sdcnmd5Item.code == sdcnmd5Temp.data.code)
                    {
                        if (sdcnmd5Temp.md5 != sdcnmd5Item.md5)
                        {
                            OutputData(fs, j, sdcnmd5Temp, false);
                            data[j] = sdcnmd5Temp;
                            if (sdcnmd5Item.volume < sdcnmd5Temp.volume)
                            {
                                bNeedOutput = true;
                                sdcnmd5Temp.delta = sdcnmd5Temp.volume - sdcnmd5Item.volume;
                            }
                        }
                        break;
                    }
                }
                if (i >= data.Count)
                {
                    OutputData(fs, i, sdcnmd5Temp, true);
                    data.Add(sdcnmd5Temp);
                    bNeedOutput = true;
                }
                if (bNeedOutput)
                {
                    path = FormRadar.strWorkDir;
                    if (mStockMarket == 0) path += @"\wShTick\";
                    else if (mStockMarket == 1) path += @"\wSzTick\";
                    path += sdcnmd5Temp.data.code;
                    path += ".dat";
                    FileStream fsTick = new FileStream(path, FileMode.Append);
                    byte[] tick = sdcnmd5Temp.GetTickData();
                    fsTick.Write(tick, 0, tick.Length);
                    fsTick.Close();
                }
            }
            fs.Close();
        }

        public void SaveDays()
        {
            foreach (StockDataCnMd5 sdcnmd5 in data)
            {
                SaveDaysItem(sdcnmd5.code, mDataDate, sdcnmd5.price, sdcnmd5.preclose, sdcnmd5.open, sdcnmd5.high, sdcnmd5.low, sdcnmd5.volume, sdcnmd5.amount);
            }
        }
    }

    sealed class StockDataHkMd5 : StockDataMd5
    {
        public StockDataHk data = null;

        public void ConvertData()
        {
            price = Convert.ToSingle(data.lasttrade);
            diff = Convert.ToSingle(data.pricechange);
            chg = Convert.ToSingle(data.changepercent);
            preclose = Convert.ToSingle(data.prevclose);
            open = Convert.ToSingle(data.open);
            high = Convert.ToSingle(data.high);
            low = Convert.ToSingle(data.low);
            volume = delta = Convert.ToSingle(data.volume);
            amount = Convert.ToSingle(data.amount);
            amplitude = low > 0 ? (high - low) * 100 / low : 0;
            int pos = data.ticktime.IndexOf(' ');
            time = Convert.ToSingle(data.ticktime.Substring(pos+1).Replace(":", ""));
            if (code == null) code = data.symbol;
            if (name == null) name = data.name;
        }
    }

    sealed class SaveStockInfoHk : SaveStockInfo
    {
        List<StockDataHkMd5> data;

        public SaveStockInfoHk() : base(2)
        {
            int pos, seek;
            data = new List<StockDataHkMd5>();
            byte[] buff = Load();
            for (int i = 0; i < mStockCount; i++)
            {
                StockDataHkMd5 sdhkmd5 = new StockDataHkMd5();
                seek = StockDataMd5.mStockDataSize * i;
                pos = seek + 248; while (pos < buff.Length) { if (buff[pos] == 0) break; pos++; }
                sdhkmd5.code = System.Text.Encoding.ASCII.GetString(buff, seek + 248, pos - seek - 248);
                pos = seek + 218; while (pos < buff.Length) { if (buff[pos] == 0) break; pos++; }
                sdhkmd5.name = System.Text.Encoding.UTF8.GetString(buff, seek + 218, pos - seek - 218);
                pos = seek;
                sdhkmd5.time = BitConverter.ToSingle(buff, pos);
                sdhkmd5.price = BitConverter.ToSingle(buff, pos + 4);
                sdhkmd5.diff = BitConverter.ToSingle(buff, pos + 8);
                sdhkmd5.chg = BitConverter.ToSingle(buff, pos + 12);
                sdhkmd5.preclose = BitConverter.ToSingle(buff, pos + 16);
                sdhkmd5.open = BitConverter.ToSingle(buff, pos + 20);
                sdhkmd5.high = BitConverter.ToSingle(buff, pos + 24);
                sdhkmd5.low = BitConverter.ToSingle(buff, pos + 28);
                sdhkmd5.amplitude = BitConverter.ToSingle(buff, pos + 32);
                sdhkmd5.volume = BitConverter.ToSingle(buff, pos + 36);
                sdhkmd5.amount = BitConverter.ToSingle(buff, pos + 40);
                sdhkmd5.buy = BitConverter.ToSingle(buff, pos + 44);
                sdhkmd5.sell = BitConverter.ToSingle(buff, pos + 48);
                sdhkmd5.delta = BitConverter.ToSingle(buff, pos + 52);
                data.Add(sdhkmd5);
            }
        }

        public void Accept(StockCheckHk sc)
        {
            int i, j, k = 0;
            bool bNeedOutput = false;
            string path = GetMarketDataFileName();
            FileStream fs = new FileStream(
                   path,
                   FileMode.OpenOrCreate,
                   FileAccess.Write,
                   FileShare.Read);
            foreach (StockDataHk sdhk in sc.result.data)
            {
                StockDataHkMd5 sdhkmd5Temp = new StockDataHkMd5();
                sdhkmd5Temp.data = sdhk;
                sdhkmd5Temp.md5 = StockTools.CreateMD5Hash(sdhk.GetString());
                sdhkmd5Temp.ConvertData();
                for (i = 0; i < data.Count; i++)
                {
                    j = k;
                    StockDataHkMd5 sdhkmd5Item = (StockDataHkMd5)data[j];
                    if (++k >= data.Count) k = 0;
                    if (sdhkmd5Item.code == sdhkmd5Temp.data.symbol)
                    {
                        if (sdhkmd5Temp.md5 != sdhkmd5Item.md5)
                        {
                            OutputData(fs, j, sdhkmd5Temp, true);
                            data[j] = sdhkmd5Temp;
                            if (sdhkmd5Item.volume < sdhkmd5Temp.volume)
                            {
                                bNeedOutput = true;
                                sdhkmd5Temp.delta = sdhkmd5Temp.volume - sdhkmd5Item.volume;
                            }
                        }
                        break;
                    }
                }
                if (i >= data.Count)
                {
                    OutputData(fs, i, sdhkmd5Temp, true);
                    data.Add(sdhkmd5Temp);
                    bNeedOutput = true;
                }
                if (bNeedOutput)
                {
                    path = FormRadar.strWorkDir;
                    path += @"\wHkTick\";
                    path += sdhkmd5Temp.data.symbol;
                    path += ".dat";
                    FileStream fsTick = new FileStream(path, FileMode.Append);
                    byte[] tick = sdhkmd5Temp.GetTickData();
                    fsTick.Write(tick, 0, tick.Length);
                    fsTick.Close();
                }
            }
            fs.Close();
        }

        public void SaveDays()
        {
            foreach (StockDataHkMd5 sdhkmd5 in data)
            {
                SaveDaysItem(sdhkmd5.code, mDataDate, sdhkmd5.price, sdhkmd5.preclose, sdhkmd5.open, sdhkmd5.high, sdhkmd5.low, sdhkmd5.volume, sdhkmd5.amount);
            }
        }

    }
    
    sealed class StockDataUsMd5 : StockDataMd5
    {
        public StockDataUs data = null;

        public void ConvertData()
        {
            price = Convert.ToSingle(data.price);
            diff = Convert.ToSingle(data.diff);
            chg = Convert.ToSingle(data.chg);
            preclose = Convert.ToSingle(data.preclose);
            open = Convert.ToSingle(data.open);
            high = Convert.ToSingle(data.high);
            low = Convert.ToSingle(data.low);
            volume = delta = Convert.ToSingle(data.volume);
            amplitude = Convert.ToSingle(data.amplitude.Replace('%',' '));
            if (code == null) code = data.symbol;
            if (name == null) name = data.cname;
        }
    }

    sealed class SaveStockInfoUs : SaveStockInfo
    {
        List<StockDataUsMd5> data;

        public SaveStockInfoUs() : base(3)
        {
            int pos, seek;
            data = new List<StockDataUsMd5>();
            byte[] buff = Load();
            for (int i = 0; i < mStockCount; i++)
            {
                StockDataUsMd5 sdusmd5 = new StockDataUsMd5();
                seek = StockDataMd5.mStockDataSize * i;
                pos = seek + 248; while (pos < buff.Length) { if (buff[pos] == 0) break; pos++; }
                sdusmd5.code = System.Text.Encoding.ASCII.GetString(buff, seek + 248, pos - seek - 248);
                pos = seek + 218; while (pos < buff.Length) { if (buff[pos] == 0) break; pos++; }
                sdusmd5.name = System.Text.Encoding.UTF8.GetString(buff, seek + 218, pos - seek - 218);
                pos = seek;
                sdusmd5.time = BitConverter.ToSingle(buff, pos);
                sdusmd5.price = BitConverter.ToSingle(buff, pos + 4);
                sdusmd5.diff = BitConverter.ToSingle(buff, pos + 8);
                sdusmd5.chg = BitConverter.ToSingle(buff, pos + 12);
                sdusmd5.preclose = BitConverter.ToSingle(buff, pos + 16);
                sdusmd5.open = BitConverter.ToSingle(buff, pos + 20);
                sdusmd5.high = BitConverter.ToSingle(buff, pos + 24);
                sdusmd5.low = BitConverter.ToSingle(buff, pos + 28);
                sdusmd5.amplitude = BitConverter.ToSingle(buff, pos + 32);
                sdusmd5.volume = BitConverter.ToSingle(buff, pos + 36);
                sdusmd5.amount = BitConverter.ToSingle(buff, pos + 40);
                sdusmd5.buy = BitConverter.ToSingle(buff, pos + 44);
                sdusmd5.sell = BitConverter.ToSingle(buff, pos + 48);
                sdusmd5.delta = BitConverter.ToSingle(buff, pos + 52);
                data.Add(sdusmd5);
            }
        }

        public void Accept(StockCheckUs sc)
        {
            int i, j, k = 0;
            bool bNeedOutput = false;
            string path = GetMarketDataFileName();
            FileStream fs = new FileStream(
                   path,
                   FileMode.OpenOrCreate,
                   FileAccess.Write,
                   FileShare.Read);
            foreach (StockDataUs sdus in sc.result.data)
            {
                StockDataUsMd5 sdusmd5Temp = new StockDataUsMd5();
                sdusmd5Temp.data = sdus;
                sdusmd5Temp.md5 = StockTools.CreateMD5Hash(sdus.GetString());
                sdusmd5Temp.ConvertData();
                for (i = 0; i < data.Count; i++)
                {
                    j = k;
                    StockDataUsMd5 sdusmd5Item = (StockDataUsMd5)data[j];
                    if (++k >= data.Count) k = 0;
                    if (sdusmd5Item.code == sdusmd5Temp.data.symbol)
                    {
                        if (sdusmd5Temp.md5 != sdusmd5Item.md5)
                        {
                            OutputData(fs, j, sdusmd5Temp, false);
                            data[j] = sdusmd5Temp;
                            if (sdusmd5Item.volume < sdusmd5Temp.volume)
                            {
                                bNeedOutput = true;
                                sdusmd5Temp.delta = sdusmd5Temp.volume - sdusmd5Item.volume;
                            }
                        }
                        break;
                    }
                }
                if (i >= data.Count)
                {
                    OutputData(fs, i, sdusmd5Temp, true);
                    data.Add(sdusmd5Temp);
                    bNeedOutput = true;
                }
                if (bNeedOutput)
                {
                    path = FormRadar.strWorkDir;
                    path += @"\wUsTick\";
                    path += sdusmd5Temp.data.symbol;
                    path += "~.dat";  // 美股存在 PRN 股票代码，无法用它做文件名，所以加个波浪号~
                    FileStream fsTick = new FileStream(path, FileMode.Append);
                    byte[] tick = sdusmd5Temp.GetTickData();
                    fsTick.Write(tick, 0, tick.Length);
                    fsTick.Close();
                }
            }
            fs.Close();
        }

        public void SaveDays()
        {
            foreach (StockDataUsMd5 sdusmd5 in data)
            {
                SaveDaysItem(sdusmd5.code, mDataDate, sdusmd5.price, sdusmd5.preclose, sdusmd5.open, sdusmd5.high, sdusmd5.low, sdusmd5.volume, sdusmd5.amount);
            }
        }

    }
}
